gtksearchbar/entry: Add [gs]et_key_capture_widget() API calls
authorCarlos Garnacho <carlosg@gnome.org>
Sun, 11 Mar 2018 12:53:17 +0000 (13:53 +0100)
committerCarlos Garnacho <carlosg@gnome.org>
Thu, 5 Apr 2018 17:26:54 +0000 (19:26 +0200)
This lets these widgets actively pull events from a widget, instead
of passively being fed events.

docs/reference/gtk/gtk4-sections.txt
gtk/gtksearchbar.c
gtk/gtksearchbar.h
gtk/gtksearchentry.c
gtk/gtksearchentry.h
gtk/gtksearchentryprivate.h

index 6c5b389736490dc1733a6c67cf6f7fc29dd49280..828580a3a2f043ede9d3e368be55415a80f005c7 100644 (file)
@@ -2397,6 +2397,8 @@ gtk_search_bar_set_search_mode
 gtk_search_bar_get_show_close_button
 gtk_search_bar_set_show_close_button
 gtk_search_bar_handle_event
+gtk_search_bar_set_key_capture_widget
+gtk_search_bar_get_key_capture_widget
 <SUBSECTION Standard>
 GTK_TYPE_SEARCH_BAR
 GTK_SEARCH_BAR
@@ -2414,6 +2416,8 @@ gtk_search_bar_get_type
 GtkSearchEntry
 gtk_search_entry_new
 gtk_search_entry_handle_event
+gtk_search_entry_set_key_capture_widget
+gtk_search_entry_get_key_capture_widget
 <SUBSECTION Standard>
 GTK_TYPE_SEARCH_ENTRY
 GTK_SEARCH_ENTRY
index 816e99d632e703b98e716c8e07295b5786e92b44..dfe6b8447cd7726749918a09a664c2cf8732aae4 100644 (file)
@@ -37,6 +37,7 @@
 #include "gtkrevealer.h"
 #include "gtksearchentryprivate.h"
 #include "gtksnapshot.h"
+#include "gtkeventcontrollerkey.h"
 
 /**
  * SECTION:gtksearchbar
  * built-in. The search bar would appear when a search is started through
  * typing on the keyboard, or the application’s search mode is toggled on.
  *
- * For keyboard presses to start a search, events will need to be
- * forwarded from the top-level window that contains the search bar.
- * See gtk_search_bar_handle_event() for example code. Common shortcuts
- * such as Ctrl+F should be handled as an application action, or through
- * the menu items.
+ * For keyboard presses to start a search, the search bar must be told
+ * of a widget to capture key events from through
+ * gtk_search_bar_set_key_capture_widget(). This widget will typically
+ * be the top-level window, or a parent container of the search bar. Common
+ * shortcuts such as Ctrl+F should be handled as an application action, or
+ * through the menu items.
  *
  * You will also need to tell the search bar about which entry you
  * are using as your search entry using gtk_search_bar_connect_entry().
@@ -86,6 +88,9 @@ typedef struct {
 
   GtkWidget   *entry;
   gboolean     reveal_child;
+
+  GtkWidget   *capture_widget;
+  GtkEventController *capture_widget_controller;
 } GtkSearchBarPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (GtkSearchBar, gtk_search_bar, GTK_TYPE_BIN)
@@ -107,24 +112,6 @@ stop_search_cb (GtkWidget    *entry,
   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), FALSE);
 }
 
-static gboolean
-entry_key_pressed_event_cb (GtkWidget    *widget,
-                            GdkEvent     *event,
-                            GtkSearchBar *bar)
-{
-  guint keyval;
-
-  gdk_event_get_keyval (event, &keyval);
-
-  if (keyval == GDK_KEY_Escape)
-    {
-      stop_search_cb (widget, bar);
-      return GDK_EVENT_STOP;
-    }
-  else
-    return GDK_EVENT_PROPAGATE;
-}
-
 static void
 preedit_changed_cb (GtkEntry  *entry,
                     GtkWidget *popup,
@@ -143,12 +130,12 @@ gtk_search_bar_handle_event_for_entry (GtkSearchBar *bar,
   guint preedit_change_id;
   gboolean res;
   char *old_text, *new_text;
-  guint keyval;
+  guint keyval, state;
 
   gdk_event_get_keyval (event, &keyval);
+  gdk_event_get_state (event, &state);
 
-
-  if (gtk_search_entry_is_keynav_event (event) ||
+  if (gtk_search_entry_is_keynav (keyval, state) ||
       keyval == GDK_KEY_space ||
       keyval == GDK_KEY_Menu)
     return GDK_EVENT_PROPAGATE;
@@ -384,6 +371,7 @@ gtk_search_bar_dispose (GObject *object)
     }
 
   gtk_search_bar_set_entry (bar, NULL);
+  gtk_search_bar_set_key_capture_widget (bar, NULL);
 
   G_OBJECT_CLASS (gtk_search_bar_parent_class)->dispose (object);
 }
@@ -512,9 +500,10 @@ gtk_search_bar_set_entry (GtkSearchBar *bar,
   if (priv->entry != NULL)
     {
       if (GTK_IS_SEARCH_ENTRY (priv->entry))
-        g_signal_handlers_disconnect_by_func (priv->entry, stop_search_cb, bar);
-      else
-        g_signal_handlers_disconnect_by_func (priv->entry, entry_key_pressed_event_cb, bar);
+        {
+          gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (priv->entry), NULL);
+          g_signal_handlers_disconnect_by_func (priv->entry, stop_search_cb, bar);
+        }
       g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry);
     }
 
@@ -524,11 +513,13 @@ gtk_search_bar_set_entry (GtkSearchBar *bar,
     {
       g_object_add_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry);
       if (GTK_IS_SEARCH_ENTRY (priv->entry))
-        g_signal_connect (priv->entry, "stop-search",
-                          G_CALLBACK (stop_search_cb), bar);
-      else
-        g_signal_connect (priv->entry, "key-press-event",
-                          G_CALLBACK (entry_key_pressed_event_cb), bar);
+        {
+          g_signal_connect (priv->entry, "stop-search",
+                            G_CALLBACK (stop_search_cb), bar);
+          gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (priv->entry),
+                                                   GTK_WIDGET (bar));
+        }
+
     }
 }
 
@@ -632,3 +623,148 @@ gtk_search_bar_set_show_close_button (GtkSearchBar *bar,
       g_object_notify (G_OBJECT (bar), "show-close-button");
     }
 }
+
+static void
+changed_cb (gboolean *changed)
+{
+  *changed = TRUE;
+}
+
+static gboolean
+capture_widget_key_handled (GtkEventControllerKey *controller,
+                            guint                  keyval,
+                            guint                  keycode,
+                            GdkModifierType        state,
+                            GtkSearchBar          *bar)
+{
+  GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+  gboolean handled;
+
+  if (priv->reveal_child)
+    return GDK_EVENT_PROPAGATE;
+
+  if (priv->entry == NULL)
+    {
+      g_warning ("The search bar does not have an entry connected to it. Call gtk_search_bar_connect_entry() to connect one.");
+      return GDK_EVENT_PROPAGATE;
+    }
+
+  if (GTK_IS_SEARCH_ENTRY (priv->entry))
+    {
+      /* The search entry was told to listen to events from the search bar, so
+       * just forward the event to self, so the search entry has an opportunity
+       * to intercept those.
+       */
+      handled = gtk_event_controller_key_forward (controller, GTK_WIDGET (bar));
+    }
+  else
+    {
+      gboolean preedit_changed, buffer_changed;
+      guint preedit_change_id, buffer_change_id;
+      gboolean res;
+
+      if (gtk_search_entry_is_keynav (keyval, state) ||
+          keyval == GDK_KEY_space ||
+          keyval == GDK_KEY_Menu)
+        return GDK_EVENT_PROPAGATE;
+
+      if (keyval == GDK_KEY_Escape)
+        {
+          if (gtk_revealer_get_reveal_child (GTK_REVEALER (priv->revealer)))
+            {
+              stop_search_cb (priv->entry, bar);
+              return GDK_EVENT_STOP;
+            }
+
+          return GDK_EVENT_PROPAGATE;
+        }
+
+      handled = GDK_EVENT_PROPAGATE;
+      preedit_changed = buffer_changed = FALSE;
+      preedit_change_id = g_signal_connect_swapped (priv->entry, "preedit-changed",
+                                                    G_CALLBACK (changed_cb), &preedit_changed);
+      buffer_change_id = g_signal_connect_swapped (priv->entry, "changed",
+                                                   G_CALLBACK (changed_cb), &buffer_changed);
+
+      res = gtk_event_controller_key_forward (controller, priv->entry);
+
+      g_signal_handler_disconnect (priv->entry, preedit_change_id);
+      g_signal_handler_disconnect (priv->entry, buffer_change_id);
+
+      if ((res && buffer_changed) || preedit_changed)
+        handled = GDK_EVENT_STOP;
+    }
+
+  if (handled == GDK_EVENT_STOP)
+    gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), TRUE);
+
+  return handled;
+}
+
+/**
+ * gtk_search_bar_set_key_capture_widget:
+ * @bar: a #GtkSearchBar
+ * @widget: (nullable) (transfer none): a #GtkWidget
+ *
+ * Sets @widget as the widget that @bar will capture key events from.
+ *
+ * If key events are handled by the search bar, the bar will
+ * be shown, and the entry populated with the entered text.
+ *
+ * Since: 3.94
+ **/
+void
+gtk_search_bar_set_key_capture_widget (GtkSearchBar *bar,
+                                      GtkWidget    *widget)
+{
+  GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+
+  g_return_if_fail (GTK_IS_SEARCH_BAR (bar));
+  g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
+
+  if (priv->capture_widget == widget)
+    return;
+
+  if (priv->capture_widget)
+    {
+      g_clear_object (&priv->capture_widget_controller);
+      g_object_remove_weak_pointer (G_OBJECT (priv->capture_widget),
+                                    (gpointer *) &priv->capture_widget);
+    }
+
+  priv->capture_widget = widget;
+
+  if (widget)
+    {
+      g_object_add_weak_pointer (G_OBJECT (priv->capture_widget),
+                                 (gpointer *) &priv->capture_widget);
+
+      priv->capture_widget_controller = gtk_event_controller_key_new (widget);
+      gtk_event_controller_set_propagation_phase (priv->capture_widget_controller,
+                                                  GTK_PHASE_CAPTURE);
+      g_signal_connect (priv->capture_widget_controller, "key-pressed",
+                        G_CALLBACK (capture_widget_key_handled), bar);
+      g_signal_connect (priv->capture_widget_controller, "key-released",
+                        G_CALLBACK (capture_widget_key_handled), bar);
+    }
+}
+
+/**
+ * gtk_search_bar_get_key_capture_widget:
+ * @bar: a #GtkSearchBar
+ *
+ * Gets the widget that @bar is capturing key events from.
+ *
+ * Returns: The key capture widget.
+ *
+ * Since: 3.94
+ **/
+GtkWidget *
+gtk_search_bar_get_key_capture_widget (GtkSearchBar *bar)
+{
+  GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+
+  g_return_val_if_fail (GTK_IS_SEARCH_BAR (bar), NULL);
+
+  return priv->capture_widget;
+}
index 5f94b371553f2cabe4cbbdee9a0f395b293bf74d..42e895b3ef2973839c20570f0016ad35b9d106d7 100644 (file)
@@ -96,6 +96,11 @@ GDK_AVAILABLE_IN_ALL
 gboolean    gtk_search_bar_handle_event    (GtkSearchBar *bar,
                                             GdkEvent     *event);
 
+GDK_AVAILABLE_IN_ALL
+void        gtk_search_bar_set_key_capture_widget (GtkSearchBar *bar,
+                                                   GtkWidget    *widget);
+GtkWidget * gtk_search_bar_get_key_capture_widget (GtkSearchBar *bar);
+
 G_END_DECLS
 
 #endif /* __GTK_SEARCH_BAR_H__ */
index 6cf7da69175a38e02344a0e558d0dddf63268fdf..7501ef78bd280b8a592c11eeb4be688c39c5456d 100644 (file)
@@ -34,6 +34,7 @@
 #include "gtkintl.h"
 #include "gtkmarshalers.h"
 #include "gtkstylecontext.h"
+#include "gtkeventcontrollerkey.h"
 
 /**
  * SECTION:gtksearchentry
@@ -63,7 +64,8 @@
  *
  * Often, GtkSearchEntry will be fed events by means of being
  * placed inside a #GtkSearchBar. If that is not the case,
- * you can use gtk_search_entry_handle_event() to pass events.
+ * you can use gtk_search_entry_set_key_capture_widget() to let it
+ * capture key input from another widget.
  */
 
 enum {
@@ -77,6 +79,9 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
+  GtkWidget *capture_widget;
+  GtkEventController *capture_widget_controller;
+
   guint delayed_changed_id;
   gboolean content_changed;
   gboolean search_stopped;
@@ -131,6 +136,8 @@ gtk_search_entry_finalize (GObject *object)
   if (priv->delayed_changed_id > 0)
     g_source_remove (priv->delayed_changed_id);
 
+  gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (object), NULL);
+
   G_OBJECT_CLASS (gtk_search_entry_parent_class)->finalize (object);
 }
 
@@ -378,16 +385,9 @@ gtk_search_entry_new (void)
 }
 
 gboolean
-gtk_search_entry_is_keynav_event (GdkEvent *event)
+gtk_search_entry_is_keynav (guint           keyval,
+                            GdkModifierType state)
 {
-  GdkModifierType state = 0;
-  guint keyval;
-
-  if (!gdk_event_get_keyval (event, &keyval))
-    return FALSE;
-
-  gdk_event_get_state (event, &state);
-
   if (keyval == GDK_KEY_Tab       || keyval == GDK_KEY_KP_Tab ||
       keyval == GDK_KEY_Up        || keyval == GDK_KEY_KP_Up ||
       keyval == GDK_KEY_Down      || keyval == GDK_KEY_KP_Down ||
@@ -433,14 +433,15 @@ gtk_search_entry_handle_event (GtkSearchEntry *entry,
 {
   GtkSearchEntryPrivate *priv = GET_PRIV (entry);
   gboolean handled;
-  guint keyval;
+  guint keyval, state;
 
   if (!gtk_widget_get_realized (GTK_WIDGET (entry)))
     gtk_widget_realize (GTK_WIDGET (entry));
 
   gdk_event_get_keyval (event, &keyval);
+  gdk_event_get_state (event, &state);
 
-  if (gtk_search_entry_is_keynav_event (event) ||
+  if (gtk_search_entry_is_keynav (keyval, state) ||
       keyval == GDK_KEY_space ||
       keyval == GDK_KEY_Menu)
     return GDK_EVENT_PROPAGATE;
@@ -452,3 +453,96 @@ gtk_search_entry_handle_event (GtkSearchEntry *entry,
 
   return handled && priv->content_changed && !priv->search_stopped ? GDK_EVENT_STOP : GDK_EVENT_PROPAGATE;
 }
+
+static gboolean
+capture_widget_key_handled (GtkEventControllerKey *controller,
+                            guint                  keyval,
+                            guint                  keycode,
+                            GdkModifierType        state,
+                            GtkWidget             *entry)
+{
+  GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (GTK_SEARCH_ENTRY (entry));
+  gboolean handled;
+
+  if (gtk_search_entry_is_keynav (keyval, state) ||
+      keyval == GDK_KEY_space ||
+      keyval == GDK_KEY_Menu)
+    return FALSE;
+
+  priv->content_changed = FALSE;
+  priv->search_stopped = FALSE;
+
+  handled = gtk_event_controller_key_forward (controller, entry);
+
+  return handled && priv->content_changed && !priv->search_stopped ? GDK_EVENT_STOP : GDK_EVENT_PROPAGATE;
+}
+
+/**
+ * gtk_search_entry_set_key_capture_widget:
+ * @bar: a #GtkSearchEntry
+ * @widget: (nullable) (transfer none): a #GtkWidget
+ *
+ * Sets @widget as the widget that @entry will capture key events from.
+ *
+ * Key events are consumed by the search entry to start or
+ * continue a search.
+ *
+ * If the entry is part of a #GtkSearchBar, it is preferable
+ * to call gtk_search_bar_set_key_capture_widget() instead, which
+ * will reveal the entry in addition to triggering the search entry.
+ *
+ * Since: 3.94
+ **/
+void
+gtk_search_entry_set_key_capture_widget (GtkSearchEntry *entry,
+                                         GtkWidget      *widget)
+{
+  GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry);
+
+  g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
+  g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
+
+  if (priv->capture_widget)
+    {
+      g_object_unref (priv->capture_widget_controller);
+      g_object_remove_weak_pointer (G_OBJECT (priv->capture_widget),
+                                    (gpointer *) &priv->capture_widget);
+    }
+
+  priv->capture_widget = widget;
+
+  if (widget)
+    {
+      g_object_add_weak_pointer (G_OBJECT (priv->capture_widget),
+                                 (gpointer *) &priv->capture_widget);
+
+      priv->capture_widget_controller = gtk_event_controller_key_new (widget);
+      gtk_event_controller_set_propagation_phase (priv->capture_widget_controller,
+                                                  GTK_PHASE_CAPTURE);
+      g_signal_connect (priv->capture_widget_controller, "key-pressed",
+                        G_CALLBACK (capture_widget_key_handled), entry);
+      g_signal_connect (priv->capture_widget_controller, "key-released",
+                        G_CALLBACK (capture_widget_key_handled), entry);
+    }
+}
+
+/**
+ * gtk_search_entry_get_key_capture_widget:
+ * @entry: a #GtkSearchEntry
+ *
+ * Gets the widget that @entry is capturing key events from.
+ *
+ * Returns: The key capture widget.
+ *
+ * Since: 3.94
+ **/
+GtkWidget *
+gtk_search_entry_get_key_capture_widget (GtkSearchEntry *entry)
+{
+  GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry);
+
+  g_return_val_if_fail (GTK_IS_SEARCH_ENTRY (entry), NULL);
+
+  return priv->capture_widget;
+}
+
index 2a8d562df6e872dfcac2350ee4605f9cf9453cf1..cdabb9fcda887bab03ec3c92932806b0b0883c78 100644 (file)
@@ -71,6 +71,14 @@ GDK_AVAILABLE_IN_ALL
 gboolean        gtk_search_entry_handle_event   (GtkSearchEntry *entry,
                                                  GdkEvent       *event);
 
+GDK_AVAILABLE_IN_ALL
+void            gtk_search_entry_set_key_capture_widget (GtkSearchEntry *entry,
+                                                         GtkWidget      *widget);
+GDK_AVAILABLE_IN_ALL
+GtkWidget*      gtk_search_entry_get_key_capture_widget (GtkSearchEntry *entry);
+
+
+
 G_END_DECLS
 
 #endif /* __GTK_SEARCH_ENTRY_H__ */
index 1e913ff2a20394fb2667a0bb31282a2048d4e4ca..1b0706d412d7a56c76875ce1d4c75a03cffcb9ee 100644 (file)
@@ -29,7 +29,8 @@
 
 G_BEGIN_DECLS
 
-gboolean gtk_search_entry_is_keynav_event (GdkEvent *event);
+gboolean gtk_search_entry_is_keynav (guint           keyval,
+                                     GdkModifierType state);
 
 G_END_DECLS